painless script的使用

简介

Painless 是一种简单、安全的脚本语言,专为与 Elasticsearch 一起使用而设计。它是 Elasticsearch 的默认脚本语言,可以安全地用于内联和存储脚本。

  • 高性能:运行速度非常快;
  • 安全性:具有方法调用/字段粒度的细粒度白名单;
  • 变量和参数可以使用显示类型或动态的可变类型;
  • 语法:扩展自 java 语法,使用方便;
  • 优化:专为 elasticsearch 设计

使用场景:script_scorescript_fieldsupdate_by_queryupdatepipeline 以及 script 聚合等等。

使用方法

总的来说, painless 的语法与 java 基本一致,常规的条件判断 iffor 循环、while、新建对象 new 等用法都是一样的,下面主要记录了一些有区别,以及特殊的地方

取值方式

painless 来说,文档的数据一般存储在 ctxctx._sourcedoc 里,取值方式类似 javascript 的方式,通过 .fieldname 或者 ['fieldname'] 取值均可。

根据常用的 API ,可以大致分为以下几个:

  • script_scorescript_fieldsortfilter 等存储在 doc 中,查询需要使用 doc['field'].value 或者 params['_source']['field'] 来取值;
  • _update_by_queryreindex 等存储在 ctx._source 中,需要使用 ctx._source.field 取值;
  • ingest pipeline 存储在 ctx 中,直接使用 ctx.field 取值;
  • 特例: foreach processor 遍历处理器,需要使用 _ingest._value 来取值

详情可参考 painless context, 需要注意的是 params['_source']['field'] 这种取值方式在 painless 的文档中没有明确说明,需参考在 script_fields 中的说明

关键词

if else while do for
in continue break return new
try catch throw this instanceof

for循环

  • for 循环既可以使用普通的 fori 循环也可以使用 for(int i : xxx) 这种语法糖;

  • 获取数组长度时,使用 .length.size() 两种取值方式均可

日期操作

painless 会将 mapping 映射为 date 类型的字段当做 zonedateTime 来处理,因此支持 getYeargetDayOfWeekgetMillis 等操作,但在编写脚本时,省略掉了 get 前缀,只需要小写方法名称的其余部分,例如:

1
2
3
4
5
6
7
8
9
10
GET hockey/_search
{
"script_fields": {
"birth_year": {
"script": {
"source": "doc.born.value.year + ',' + doc.born.value.monthValue"
}
}
}
}

正则表达式

出于正则表达式的性能原因,painless 中默认是禁用了的,需要在配置文件中开启:

1
script.painless.regex.enabled: true

使用正则时需要以 / 符号作为正则的起始和结束标志,正则表达式写在斜线的中间部分,列如,匹配字符串是否以 b 开头:

1
2
3
4
5
6
7
8
9
10
11
12
13
POST hockey/_update_by_query
{
"script": {
"lang": "painless",
"source": """
if (ctx._source.last =~ /b/) {
ctx._source.last += "matched";
} else {
ctx.op = "noop";
}
"""
}
}

额外的动态类型 def

1
2
3
def dp = 1;
def dr = new ArrayList();
dr = dp;

painless 函数声明及运作方式

s.foo(a,b) 调用为例,painless 在执行时会先获取到类 s 然后查找带有两个参数以及方法名为 foo 的方法进行调用,这种方式使得 painless 不像 java 那样支持重载,列如 java 中的 Matcher 类有两个方法 group(int)group(String) ,由于他们的参数数量和方法名都相同,painless 将无法分辨,所以 painless 中使用 group(int)namedGroup(String) 两个方法来进行区分

函数声明:

在调用方法前声明函数即可,在 painless 中的 String 类是不具备 split 函数的,如下的示例展示了一个自定义的 split 和使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
POST index_a/_doc
{
"a" : "aaaa",
"b" : "asdf,asgeg,geg,saeg"
}
POST index_a/_update_by_query
{
"script": {
"source": """
String[] split(String s, char d) {
int count = 0;
for (char c : s.toCharArray()) {
if (c == d) {
++count;
}
}
if (count == 0) {
return new String[] {s};
}
String[] r = new String[count + 1];
int i0 = 0, i1 = 0;
count = 0;
for (char c : s.toCharArray()) {
if (c == d) {
r[count++] = s.substring(i0, i1);
i0 = i1 + 1;
}
++i1;
}
r[count] = s.substring(i0, i1);
return r;
}
ctx._source.aaa = split('a,b,c',(char)',');
"""
}
}

支持的API

painless 中支持的 API 可参考这个地址

0%